
// INCLUDES ********************************************************************

#include "Texture2D.h"

#include "FileUtils.h"

#include "MathUtils.h"

#include "Configuration.h"

#include <stdexcept>

#include <ImageIO/ImageIO.h>



extern const char* ConfigurationKeys[];

// NAMESPACES ******************************************************************

using namespace Meteor;

// *****************************************************************************

#pragma mark -
#pragma mark Constructors
// -----------------------------------------------------------------------------

Texture2D::Texture2D() : mSize( 0.0f, 0.0f), 
                         mTextureCoordinates( gDefaultTextureCoordinates, 
                                              gDefaultTextureCoordinates+gNumTextureCoordinates)
{
}

// -----------------------------------------------------------------------------

Texture2D::Texture2D( const std::string& filename) : 
mSize( 0.0f, 0.0f),
mTextureCoordinates( gDefaultTextureCoordinates, 
                     gDefaultTextureCoordinates+gNumTextureCoordinates),
mAssociatedFilename( filename)
{
    LoadFile( filename);
}

// -----------------------------------------------------------------------------

Texture2D::Texture2D( const void* const data,
                      const Texture2DPixelFormat format,
                      const Vector2D& size) 
: mSize( 0.0f, 0.0f), mTextureCoordinates( gDefaultTextureCoordinates, 
                                           gDefaultTextureCoordinates+gNumTextureCoordinates)
{
    mPixelFormat = format;
    
    CreateTexture( data, size.X(), size.Y());
}

#pragma mark -
#pragma mark Destructor
// -----------------------------------------------------------------------------

Texture2D::~Texture2D()
{
    glDeleteTextures( 1, &mName);
}

#pragma mark -
#pragma mark File I/O
// -----------------------------------------------------------------------------

void Texture2D::LoadFile( const std::string& file)
{
    CFStringRef nameAsCFString = CFStringCreateWithCString( kCFAllocatorDefault, 
                                                            file.c_str(), 
                                                            kCFStringEncodingUTF8);
    
    CFURLRef fileCFURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, 
                                                        nameAsCFString, 
                                                        kCFURLPOSIXPathStyle,
                                                        false);

    CGImageSourceRef imageSource = CGImageSourceCreateWithURL( fileCFURL, 0);
    
    if ( imageSource == nullptr)
    {
        Logger::DebugLog( "Failed to create CGImage for %s", file.c_str());
        
        CFRelease( fileCFURL);
        
        return;
    }
    
    CGImageRef image = CGImageSourceCreateImageAtIndex( imageSource, 0, 0);
    
    CFRelease(imageSource);
    
    CFRelease( nameAsCFString);
    
    CFRelease( fileCFURL);

    GetImageProperties( image);
	    
    int width = static_cast<int> ( mSize.X());
    int height = static_cast<int> ( mSize.Y());
    
    if ( image == nullptr || width == 0 || height == 0 )
    {
        throw std::invalid_argument( "Provided file does not have a valid size ( width or height is 0).");
        
        return;
    }

    GLubyte* data = new GLubyte[ width * height * 4];
     
    const CGContextRef spriteContext = CGBitmapContextCreate( data, 
                                                              width, 
                                                              height, 
                                                              gBitsPerComponent, 
                                                              width * 4, 
                                                              CGImageGetColorSpace( image),
                                                              kCGImageAlphaPremultipliedLast);
    
    CGContextDrawImage( spriteContext, 
                        CGRectMake( 0.0, 0.0, ( CGFloat) mSize.X(), ( CGFloat) mSize.Y()),
                        image);
    
    if ( mPixelFormat == kTexture2DPixelFormat_RGB565) 
    {
        TransformImageDataTo565( &data, width, height);
	}
    
    CreateTexture( data, width, height);

    // cleanup
    delete[] data;
    
    CGContextRelease( spriteContext);
    
    CFRelease( image);
}

#pragma mark -
#pragma mark Properties
// -----------------------------------------------------------------------------

void Texture2D::TransformImageDataTo565( GLubyte** data,
                                         const int width, 
                                         const int height)
{
    GLubyte* tempData = new GLubyte[height * width * 2];
    
    unsigned int* inPixel32 = reinterpret_cast<unsigned int*>( *data);
    
    unsigned short* outPixel16 = reinterpret_cast<unsigned short*> ( tempData);
    
    for ( int pixel = 0; pixel < width * height; ++pixel, ++inPixel32)
    {
        *outPixel16++ = ( ( ( ( *inPixel32 >> 0) & 0xFF) >> 3) << 11) | 
                        ( ( ( ( *inPixel32 >> 8) & 0xFF) >> 2) << 5) | 
                        ( ( ( ( *inPixel32 >> 16) & 0xFF) >> 3) << 0);
    }
    
    delete[] *data;
    
    *data = tempData;
}

// -----------------------------------------------------------------------------

void Texture2D::GetImageProperties( const CGImageRef image)
{
    const size_t width = CGImageGetWidth( image);
	const size_t height = CGImageGetHeight( image);
    
    if ( Configuration::Instance().ValueForKey<bool>( "NPOT support") == true)
    {
        mSize.SetX( width);
        mSize.SetY( height);
    }
    else
    {
        mSize.SetX( MathUtils::NextPowerOfTwo( width));
        mSize.SetY( MathUtils::NextPowerOfTwo( height));
    }

    //const CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo( image);
    /*
	const bool hasAlpha = ( ( alphaInfo == kCGImageAlphaPremultipliedLast) || 
                            ( alphaInfo == kCGImageAlphaPremultipliedFirst) || 
                            ( alphaInfo == kCGImageAlphaLast) || 
                            ( alphaInfo == kCGImageAlphaFirst) ? true : false);
                            */
    
    mPixelFormat = kTexture2DPixelFormat_RGBA8888;
}

// -----------------------------------------------------------------------------

void Texture2D::CreateTexture( const void* const imageData,
                               const int width, 
                               const int height)
{
    glGenTextures( 1, &mName);
 
    GLint currentTexture;
          
    glGetIntegerv( GL_TEXTURE_BINDING_2D, &currentTexture);
 
    glBindTexture( GL_TEXTURE_2D, mName);
    
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    int internalFormat;
    
    int type;
    
    switch ( mPixelFormat) 
    {
        case kTexture2DPixelFormat_RGBA8888:
            
            internalFormat = GL_RGBA;
            type = GL_UNSIGNED_BYTE;

        break;
            
        case kTexture2DPixelFormat_RGB565:
            
            internalFormat = GL_RGB;
            type = GL_UNSIGNED_SHORT_5_6_5;
           
        break;
            
        case kTexture2DPixelFormat_A8:
        
            internalFormat = GL_ALPHA;
            type = GL_UNSIGNED_BYTE;
            
        break;
            
        default:
            internalFormat = GL_RGBA;
            type = GL_UNSIGNED_BYTE;		
    }
    
    glTexImage2D( GL_TEXTURE_2D, 
                  0, 
                  internalFormat, 
                  mSize.X(),
                  mSize.Y(),
                  0, 
                  internalFormat, 
                  type, 
                  imageData);
    
    glBindTexture( GL_TEXTURE_2D, currentTexture);
}

#pragma mark -
#pragma mark Getter
// -----------------------------------------------------------------------------

const GLuint Texture2D::Name() const
{
    return mName;
}

// -----------------------------------------------------------------------------

const float Texture2D::Width() const
{
    return mSize.X();
}

// -----------------------------------------------------------------------------

const float Texture2D::Height() const
{
    return mSize.Y();
}

// -----------------------------------------------------------------------------

const std::string Texture2D::Filename() const
{
    return mAssociatedFilename.substr( mAssociatedFilename.find_last_of("/"), mAssociatedFilename.size());
}


#pragma mark -
#pragma mark Setter
// -----------------------------------------------------------------------------

void Texture2D::SetRect( const Rectangle& rect)
{    
    mTextureCoordinates[0] = rect.X() / mSize.X();
    mTextureCoordinates[1] = ( rect.Y() + rect.Height()) / mSize.Y();
    
    mTextureCoordinates[2] = ( rect.X() + rect.Width()) / mSize.X();
    mTextureCoordinates[3] = ( rect.Y() + rect.Height()) / mSize.Y();
   
    mTextureCoordinates[4] = rect.X() / mSize.X();
    mTextureCoordinates[5] = rect.Y() / mSize.Y();
    
    mTextureCoordinates[6] = ( rect.X() + rect.Width()) / mSize.X();
    mTextureCoordinates[7] = rect.Y() / mSize.Y();
}

// -----------------------------------------------------------------------------

void Texture2D::SetParameter( const GLenum name, const GLenum value)
{
    glBindTexture( GL_TEXTURE_2D, mName);
    
    glTexParameteri( GL_TEXTURE_2D, name, value);
    
    glBindTexture( GL_TEXTURE_2D, mName);
}

#pragma mark -
#pragma mark Drawing
// -----------------------------------------------------------------------------

void Texture2D::Draw() const
{    
    glEnable( GL_TEXTURE_2D);
    
    glBindTexture( GL_TEXTURE_2D, mName);
	
    glTexCoordPointer( 2, GL_FLOAT, 0, &mTextureCoordinates[0]);
	
    glDrawArrays( GL_TRIANGLE_STRIP, 0, 4);
    
    glDisable( GL_TEXTURE_2D);
}


